<?php

namespace Ktnw\nacos;

use Exception;

/**
 * nacos openAPI的工具类
 */
class NacosUtils
{
    private static $currentToken = null;
    private static $dataGroup    = "DEFAULT_GROUP";

    /**
     * 注册到Nacos
     * @return bool
     * @throws Exception
     */
    public static function register(): bool
    {
        if (self::isRegister()) {
            return true;
        }

        if (!self::checkServerOnlineStatus()) {
            return false;
        }

        $apiUrl = "/nacos/v1/ns/instance";
        $params = [
            ParamConstants::SERVICE_NAME => self::getServerName(),
            ParamConstants::IP           => self::getServerIp(),
            ParamConstants::PORT         => self::getServerPort(),
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        $r      = HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'POST', [], 20);
        if (empty($r)) {
            return false;
        }
        return is_string($r) && strtoupper($r) == "OK";
    }

    /**
     * 发送心跳
     * 若未注册，会先注册到Nacos中.
     * 一次心跳，有效期默认为15秒。建议10秒发送一次心跳，保证实例不下线。
     * @return false|void
     * @throws Exception
     */
    public static function beat()
    {
        if (!self::checkServerOnlineStatus()) {
            return false;
        }

        $apiUrl = "/nacos/v1/ns/instance/beat";
        $params = [
            ParamConstants::SERVICE_NAME => self::getServerName(),
            ParamConstants::BEAT         => self::getBeatInfo(),
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        $r      = HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'PUT', [], 20);
//        var_dump($r)
        if (empty($r)) {
            return false;
        }
    }

    /**
     * 获取网关地址
     * 网关地址 + 路由可调用Nacos中实例
     * @throws Exception
     */
    public static function getGatewayURL(): string
    {
        return self::getInstanceURL(self::getGatewayServerName());
    }

    /**
     * 获取Nacos中实例的访问地址
     * @param string $serverName
     * @param bool $healthyOnly
     * @param string $groupName
     * @param string $namespaceId
     * @return string
     * @throws Exception
     */
    public static function getInstanceURL(string $serverName, bool $healthyOnly = true, string $groupName = '', string $namespaceId = ''): string
    {
        $gateways = self::getInstances($serverName, $healthyOnly, $groupName, $namespaceId);
        if (empty($gateways)) {
            throw new Exception("未获取到实例[" . $serverName . "]服务");
        }
        $hosts = $gateways["hosts"];
        $host  = $hosts[array_rand($hosts, 1)];
        return self::getAccessServerPrefix() . "://" . $host["ip"] . ":" . $host["port"];
    }

    /**
     * 获取实例列表
     * @param string serviceName
     * @param bool healthyOnly
     * @param string groupName
     * @param string namespaceId
     * @return array|string
     * @throws Exception
     */
    private static function getInstances(string $serviceName, bool $healthyOnly = true, string $groupName = '', string $namespaceId = '')
    {
        $apiUrl = "/nacos/v1/ns/instance/list";
        $params = [
            ParamConstants::SERVICE_NAME => $serviceName,
            ParamConstants::HEALTHY_ONLY => $healthyOnly,
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        if (!empty($groupName)) {
            $params[ParamConstants::GROUP_NAME] = $groupName;
        }
        if (!empty($namespaceId)) {
            $params[ParamConstants::NAMESPACE_ID] = $namespaceId;
        }
        return HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'GET', [], 15);
    }

    /**
     * 登录
     * @throws Exception
     */
    private static function login(): array
    {
        $apiUrl = "/nacos/v1/auth/login";
        $params = [
            ParamConstants::USERNAME => self::getNacosUserName(),
            ParamConstants::PASSWORD => self::getNacosPassword()
        ];
        $r      = HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'POST', [], 15);
        if (empty($r)) {
            throw new Exception("未获取到NACOS服务");
        }
        return $r;
    }

    /**
     * 获取当前有效的token
     * 待优化:加入缓存
     *
     * @return mixed|string
     * @throws Exception
     */
    private static function getCurrentToken()
    {
        if (empty(self::$currentToken) || !self::checkCurrentToken()) {
            self::$currentToken = self::login();
            list($second) = explode(" ", microtime());
            self::$currentToken[ParamConstants::TOKEN_CREATE_TIME] = $second;
        }
        return self::$currentToken[ParamConstants::ACCESS_TOKEN];
    }

    /**
     * 是否已注册到Nacos
     * @throws Exception
     */
    private static function isRegister(): bool
    {
        $r = self::getInstance();
        if (empty($r) || is_string($r)) {
            return false;
        }
        return $r["status"] != 200;
    }

    /**
     * 获取nacos中的实例
     * @return array|string
     * @throws Exception
     */
    public static function getInstance()
    {
        $apiUrl = "/nacos/v1/ns/instance";
        $params = [
            ParamConstants::SERVICE_NAME => self::getServerName(),
            ParamConstants::IP           => self::getServerIp(),
            ParamConstants::PORT         => self::getServerPort(),
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        return HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'GET', [], 15);
    }

    /**
     * 服务下线
     * 服务中的实例数为0时，一段时间后，会自动删除。
     * 实现方式:
     * step1: 往nacos推送配置文件，设置服务下线标识。
     * step2: 实例注册、发送心跳时，先从nacos中获取服务上下线标识。若为下线，则不注册或发送心跳。
     * @return array|string
     * @throws Exception
     */
    public static function serverOffline()
    {
        $dataId  = self::getServerName() . "-up-down.text";
        $content = "offline";
        return self::pushConfig($dataId, self::$dataGroup, $content);
    }

    /**
     * 服务上线
     * 原理同服务下线
     * @return array|string
     * @throws Exception
     */
    public static function serverOnline()
    {
        $dataId  = self::getServerName() . "-up-down.text";
        $content = "online";
        return self::pushConfig($dataId, self::$dataGroup, $content);
    }

    /**
     * 发布配置
     * @return array|string
     * @throws Exception
     */
    public static function pushConfig(string $dataId, string $dataGroup, string $content)
    {
        $apiUrl = "/nacos/v1/cs/configs";
        $params = [
            ParamConstants::DATA_ID      => $dataId,
            ParamConstants::DATA_GROUP   => $dataGroup,
            ParamConstants::DATA_CONTENT => $content,
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        return HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'POST', [], 20);
    }


    /**
     * 删除配置
     * @param string $dataId
     * @param string $dataGroup
     * @return array|string
     * @throws Exception
     */
    public static function deleteConfig(string $dataId, string $dataGroup)
    {
        $apiUrl = "/nacos/v1/cs/configs";
        $params = [
            ParamConstants::DATA_ID      => $dataId,
            ParamConstants::DATA_GROUP   => $dataGroup,
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        return HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'DELETE', [], 20);
    }

    /**
     * 校验服务是否可以上线
     *
     * @throws Exception
     */
    private static function checkServerOnlineStatus(): bool
    {
        $dataId = self::getServerName() . "-up-down.text";
        $status = self::fetchConfigs($dataId, self::$dataGroup);
        return empty($status) || strtolower($status) != "offline";
    }

    /**
     * 获取配置
     * @param string $dataId
     * @param string $dataGroup
     * @return array|string
     * @throws Exception
     */
    public static function fetchConfigs(string $dataId, string $dataGroup)
    {
        $apiUrl = "/nacos/v1/cs/configs";
        $params = [
            ParamConstants::DATA_ID      => $dataId,
            ParamConstants::DATA_GROUP   => $dataGroup,
            ParamConstants::ACCESS_TOKEN => self::getCurrentToken()
        ];
        $r      = HttpClient::sendHttp(self::getNacosServerAddress() . $apiUrl, $params, 'GET', [], 20);
        if (is_string($r) && strpos($r, "config data not exist") !== false) {
            return "";
        }
        return $r;
    }

    /**
     * 校验token是否有效
     */
    private static function checkCurrentToken(): bool
    {
        list($second) = explode(" ", microtime());
        return $second - self::$currentToken[ParamConstants::TOKEN_CREATE_TIME] < self::$currentToken[ParamConstants::TOKEN_TTL] - 60 * 10;
    }


    private static function getNacosUserName()
    {
        return config("nacosconfig.userName");
    }

    private static function getNacosPassword()
    {
        return config("nacosconfig.password");
    }

    private static function getNacosServerAddress()
    {
        return config("nacosconfig.serverAddress");
    }

    private static function getServerName()
    {
        return config("nacosconfig.serverName");
    }

    private static function getGatewayServerName()
    {
        return config("nacosconfig.gatewayServerName");
    }

    private static function getAccessServerPrefix()
    {
        return config("nacosconfig.accessServerPrefix");
    }

    private static function getServerIp()
    {
        return $_SERVER['SERVER_ADDR'];
    }

    private static function getServerPort()
    {
        return $_SERVER['SERVER_PORT'];
    }

    private static function getBeatInfo()
    {
        $beat                               = [];
        $beat[ParamConstants::SERVICE_NAME] = self::getServerName();
        $beat[ParamConstants::IP]           = self::getServerIp();
        $beat[ParamConstants::PORT]         = self::getServerPort();
        return json_encode($beat, JSON_UNESCAPED_UNICODE);
    }


}